package org.xx.armory.commons;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import static java.util.Arrays.stream;
import static java.util.Collections.synchronizedMap;
import static java.util.Comparator.comparingInt;
import static org.xx.armory.commons.Validators.rejectIfBlank;
import static org.xx.armory.commons.Validators.rejectIfNull;

public final class ObjectUtils {
    private static final Map<Class<?>, Constructor<?>> BLANK_CONSTRUCTOR_CACHE = synchronizedMap(new WeakHashMap<>());

    /**
     * 包含类型和对应的属性访问器的映射的缓存。
     */
    private static final Map<Class<?>, Map<String, Method>> CLASS_PROPERTY_GETTERS_CACHE = new WeakHashMap<>();

    /**
     * 包含类型和对应的属性访问器的映射的缓存。
     */
    private static final Map<Class<?>, Map<String, Method>> CLASS_PROPERTY_SETTERS_CACHE = new WeakHashMap<>();

    private ObjectUtils() {
        throw new AssertionError();
    }

    private static Logger getLogger() {
        return LoggerFactory.getLogger(ObjectUtils.class);
    }

    static String toPropertyName(
            String name
    ) {
        name = rejectIfBlank(name, "name");

        if (name.length() == 1) {
            return name.toUpperCase();
        } else {
            final char cc = name.charAt(0);
            final StringBuilder result = new StringBuilder();
            if (Character.isUpperCase(name.charAt(1))) {
                result.append(cc).append(name.substring(1));
            } else {
                result.append(Character.toUpperCase(cc)).append(name.substring(1));
            }
            return result.toString();
        }
    }

    static String toFieldName(
            String name
    ) {
        final var name_ = rejectIfBlank(name, "name").trim();

        if (name_.length() == 1) {
            return name_.toLowerCase();
        } else {
            final char cc = name_.charAt(0);
            final StringBuilder result = new StringBuilder();
            if (Character.isUpperCase(name_.charAt(1))) {
                result.append(cc).append(name_.substring(1));
            } else {
                result.append(Character.toLowerCase(cc)).append(name_.substring(1));
            }
            return result.toString();
        }
    }

    private static Constructor<?> getConstructor(
            Class<?> clazz
    ) {
        // 首先尝试找到默认构造函数。
        try {
            return clazz.getConstructor();
        } catch (NoSuchMethodException ignored) {
        }

        // 如果不存在，就返回参数最少的构造函数。
        return stream(clazz.getConstructors()).min(comparingInt(Constructor::getParameterCount)).orElse(null);
    }

    /**
     * 获取指定类型的所有属性访问器。
     *
     * @param clazz
     *         指定的类型。
     * @return 指定类型的所有属性访问器。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}。
     */
    static Map<String, Method> getPropertyGetters(
            Class<?> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        final var result = new HashMap<String, Method>();

        for (final var method : clazz.getMethods()) {
            // 跳过静态方法和参数不为0的方法。
            if ((method.getModifiers() & Modifier.STATIC) != 0 || method.getParameterCount() > 0) {
                continue;
            }

            final var methodName = method.getName();
            final String propertyName;
            switch (methodName) {
                case "getClass":
                case "get":
                case "is":
                case "set":
                    // 跳过特殊方法。
                    continue;
                default:
                    if (methodName.startsWith("get")) {
                        propertyName = methodName.substring(3);
                    } else if (methodName.startsWith("is")) {
                        if (method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class) {
                            propertyName = methodName.substring(2);
                        } else {
                            continue;
                        }
                    } else {
                        continue;
                    }
            }

            method.setAccessible(true);
            result.put(toFieldName(propertyName), method);
        }

        return result;
    }

    /**
     * 获取指定类型的所有属性访问器。
     *
     * @param clazz
     *         指定的类型。
     * @return 指定类型的所有属性访问器。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}。
     */
    static Map<String, Method> getPropertySetters(
            Class<?> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        final var result = new HashMap<String, Method>();

        for (final var method : clazz.getMethods()) {
            // 跳过静态方法和参数不为1的方法。
            if ((method.getModifiers() & Modifier.STATIC) != 0 || method.getParameterCount() != 1) {
                continue;
            }

            final var methodName = method.getName();
            final String propertyName;
            switch (methodName) {
                case "getClass":
                case "get":
                case "is":
                case "set":
                    // 跳过特殊方法。
                    continue;
                default:
                    if (methodName.startsWith("set")) {
                        propertyName = methodName.substring(3);
                    } else {
                        continue;
                    }
            }

            method.setAccessible(true);
            result.put(toFieldName(propertyName), method);
        }

        return result;
    }

    /**
     * 创建实例。
     * <p>通过构造函数来创建实例，如果构造函数存在参数，那么所有的参数都传入默认值。</p>
     *
     * @param clazz
     *         实体类型。
     * @param <T>
     *         实体类型。
     * @return 已创建的实体。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}。
     */
    public static <T> T createInstance(
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        final var ctor = BLANK_CONSTRUCTOR_CACHE.computeIfAbsent(clazz, ObjectUtils::getConstructor);

        if (ctor == null) {
            throw new IllegalArgumentException("Cannot find public constructors of " + clazz.getTypeName());
        }

        ctor.setAccessible(true);

        // 首先尝试使用默认构造函数构造实体。
        try {
            final var values = new ArrayList<>(ctor.getParameterCount());
            for (final var pt : ctor.getParameterTypes()) {
                values.add(defaultValue(pt));
            }
            return clazz.cast(ctor.newInstance(values.toArray()));
        } catch (ReflectiveOperationException ex) {
            throw new IllegalStateException("Cannot create instance of " + clazz.getTypeName(), ex);
        }
    }

    public static void setProperty(
            Object bean,
            String name,
            Object value
    )
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        rejectIfNull(bean, "bean");
        name = rejectIfBlank(name, "name").trim();

        final var method = CLASS_PROPERTY_SETTERS_CACHE
                .computeIfAbsent(bean.getClass(), ObjectUtils::getPropertySetters)
                .get(toFieldName(name));

        if (method == null) {
            throw new NoSuchMethodException("Cannot access property " + name);
        }

        method.invoke(bean, value);
    }

    public static Object getProperty(
            Object bean,
            String name
    )
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        rejectIfNull(bean, "bean");
        name = rejectIfBlank(name, "name").trim();

        final var method = CLASS_PROPERTY_GETTERS_CACHE
                .computeIfAbsent(bean.getClass(), ObjectUtils::getPropertyGetters)
                .get(toFieldName(name));

        if (method == null) {
            throw new NoSuchMethodException("Cannot access property " + name);
        }

        return method.invoke(bean);
    }

    /**
     * 获取指定类型的默认值。
     * <ul>
     * <li>对于数值类型（包括char），返回{@code 0}。</li>
     * <li>对于布尔类型，返回{@code false}。</li>
     * <li>对于字符串类型或者 {@link CharSequence}，返回空字符串。</li>
     * <li>对于其它引用类型，返回{@code null}。</li>
     * </ul>
     *
     * @param clazz
     *         指定的类型。
     * @return 默认值。
     * @throws IllegalArgumentException
     *         如果参数{@code clazz}是{@code null}。
     */
    public static Object defaultValue(
            Class<?> clazz
    ) {
        rejectIfNull(clazz, "clazz");
        if (clazz.equals(char.class)) {
            return (char) 0;
        } else if (clazz.equals(byte.class)) {
            return (byte) 0;
        } else if (clazz.equals(short.class)) {
            return (short) 0;
        } else if (clazz.equals(int.class)) {
            return 0;
        } else if (clazz.equals(long.class)) {
            return 0L;
        } else if (clazz.equals(float.class)) {
            return 0F;
        } else if (clazz.equals(double.class)) {
            return 0D;
        } else if (clazz.equals(boolean.class)) {
            return false;
        } else if (clazz.equals(String.class) || clazz.equals(CharSequence.class)) {
            return "";
        } else if (clazz.isEnum()) {
            final var ecs = clazz.getEnumConstants();
            return ecs != null && ecs.length > 0 ? ecs[0] : null;
        } else {
            return null;
        }
    }

    /**
     * 判断指定的对象是否 {@code null} 或者只包含空白字符。
     *
     * @param o
     *         待判断的对象。
     * @return 是否 {@code null} 或者只包含空白字符。
     */
    public static boolean isNullOrBlank(
            Object o
    ) {
        if (o == null) {
            return true;
        }
        if (o instanceof CharSequence) {
            final var cs = ((CharSequence) o).toString();
            return cs.isBlank();
        }

        return false;
    }

    /**
     * 将指定的对象转化为属性名值映射。
     *
     * @param obj
     *         待转化的对象。
     * @return 包含该对象的所有公共属性名值的映射，如果参数{@code obj}是{@code null}则返回空映射。
     */
    public static Map<String, Object> toMap(
            Object obj
    ) {
        final var result = new HashMap<String, Object>();

        if (obj != null) {
            final var clazz = obj.getClass();
            final var getters = CLASS_PROPERTY_GETTERS_CACHE.computeIfAbsent(clazz, ObjectUtils::getPropertyGetters);

            for (final var entry : getters.entrySet()) {
                final var key = entry.getKey();
                final var getter = entry.getValue();
                final Object value;
                try {
                    value = getter.invoke(obj);
                } catch (ReflectiveOperationException ex) {
                    throw new IllegalStateException("Cannot get " + clazz.getTypeName() + "." + key, ex);
                }

                if (value != null) {
                    result.put(key, value);
                }
            }
        }

        return result;
    }
}
